/******************************************************************************

.../UniqueInstance.m

Copyright Michael T. H. Scott, 1995.

UniqueInstance is a class which can have only one instance. Alloc, copy and free return error.

NOTE InterfaceBuilder uses _zoneAlloc() to create an instance of a custom object. It does not call +alloc. By overriding _zoneAlloc() for classes which respond to +uniqueInstance we guarantee that only one instance is ever allocated. It is necessary to remember that if multiple instances of a UniqueInstance subclass are included in an application's nib files then -init will be multiply called on that instance.
******************************************************************************/

#import "UniqueInstance.h"
#import <objc/objc-runtime.h>

static id uniqueInstances = nil;
static id initialized = nil;
static const char *msg = "does not %s.\n";
static id (*runtimeZoneAlloc)();
static id uniqueInstanceZoneAlloc(Class aClass,
								  unsigned int indexedIvarBytes,
								  NXZone *zone);

@implementation UniqueInstance

/*-----------------------------------------------------------------------------
Initializes the HashTable which will contain the unique instance for each UniqueInstance subclass.
-----------------------------------------------------------------------------*/
+ initialize
{
	if ( self == [UniqueInstance class] )
	{
		uniqueInstances = [[HashTable allocFromZone: [self zone]]
									  initKeyDesc: "@"
									  valueDesc: "@"];
									  
		initialized = [[List allocFromZone: [self zone]] init];
									  
		runtimeZoneAlloc = _zoneAlloc;
		_zoneAlloc = uniqueInstanceZoneAlloc;
	}
		
	return self;
}

/*-----------------------------------------------------------------------------
Returns the unique instance of the receiving class.
-----------------------------------------------------------------------------*/
+ uniqueInstance
{
	const void *key = (const void *)self;
	id uniqueInstance = (id)[uniqueInstances valueForKey: key];
	
	if ( !uniqueInstance )
	{
		uniqueInstance = runtimeZoneAlloc(self, 0U, [self zone]);
		[uniqueInstances insertKey: key value: (void *)uniqueInstance];
		[uniqueInstance init];
		[initialized addObject: uniqueInstance];
	}
	
	return uniqueInstance;
}

/*-----------------------------------------------------------------------------
Reimplemented to return error.
-----------------------------------------------------------------------------*/
+ alloc
{
	return [self error: msg, sel_getName(_cmd)];
}

/*-----------------------------------------------------------------------------
Returns whether the receiver is uninitialized.
-----------------------------------------------------------------------------*/
- (BOOL)uninitialized
{
	return [initialized indexOf: self] == NX_NOT_IN_LIST;
}

/*-----------------------------------------------------------------------------
Designated initializer.
-----------------------------------------------------------------------------*/
- init
{
	if ( [self uninitialized] ) [super init];
	
	return self;
}

/*-----------------------------------------------------------------------------
Reimplemented to return error.
-----------------------------------------------------------------------------*/
- copyFromZone: (NXZone *)aZone
{
	return [self error: msg, sel_getName(_cmd)];
}

/*-----------------------------------------------------------------------------
Reimplemented to return error.
-----------------------------------------------------------------------------*/
- free
{
	return [self error: msg, sel_getName(_cmd)];
}

@end

/*-----------------------------------------------------------------------------
Overrides _zoneAlloc to return the unique instance of a UniqueInstance subclasses.
-----------------------------------------------------------------------------*/
static id uniqueInstanceZoneAlloc(Class aClass,
					  			  unsigned int indexedIvarBytes,
								  NXZone *zone)
{
	if ( [aClass respondsTo: @selector(uniqueInstance)] )
	{
		return [aClass uniqueInstance];
	}
	
	return runtimeZoneAlloc(aClass, indexedIvarBytes, zone);
}
